# +----------------------------------------------------------------------
# | ChatWork智能聊天办公系统
# +----------------------------------------------------------------------
# | 软件声明: 本系统并非自由软件,未经授权任何形式的商业使用均属非法。
# | 版权保护: 任何企业和个人不允许对程序代码以任何形式任何目的复制/分发。
# | 授权要求: 如有商业使用需求,请务必先与版权所有者取得联系并获得正式授权。
# +----------------------------------------------------------------------
# | Author: ChatWork Team <2474369941@qq.com>
# +----------------------------------------------------------------------
import time
from typing import Dict, List
from pydantic import TypeAdapter
from exception import AppException
from hypertext import PagingResult
from tortoise.transactions import in_transaction
from plugins.safe.driver import SecurityDriver
from common.enums.client import ClientEnum
from common.enums.public import GenderEnum
from common.enums.wallet import WalletEnum
from common.utils.urls import UrlUtil
from common.utils.times import TimeUtil
from common.utils.tools import ToolsUtil
from common.utils.valid import ValidUtils
from common.models.users import UserGroupModel
from common.models.users import UserWalletModel
from common.models.users import UserModel, UserSubscribeModel
from common.models.market import SubscribePackageModel
from common.service.subscribe_logic import SubscribeLogic
from apps.admin.schemas.users import user_schema as schema
from apps.api.widgets.user_widget import UserWidget


class UserService:
    """ 用户服务类 """

    @classmethod
    async def sessions(cls, params: schema.UserSessionIn) -> PagingResult[schema.UserSessionListVo]:
        """
        会话列表。

        Args:
            params (schema.UserSessionIn): 会话搜索参数。

        Author:
            zero
        """
        init_sessions = await SecurityDriver.module("api").get_token_list(params.user_id)

        start_index = (params.page_no - 1) * params.page_size
        end_index = start_index + params.page_size
        if start_index < 0:
            start_index = 0
        if end_index > len(init_sessions):
            end_index = len(init_sessions)

        sessions = init_sessions[start_index:end_index]

        _data = []
        for item in sessions:
            tips = "在线"
            status = 1
            if item.get("kick_out"):
                tips = "踢出"
                status = 3
            elif item.get("expire_time") <= int(time.time()):
                tips = "过期"
                status = 2

            surplus_time = item.get("expire_time", 0) - int(time.time())
            surplus_time = "0(s)" if surplus_time <= 0 else str(surplus_time) + "(s)"

            _data.append(schema.UserSessionListVo(
                uuid=item["key"],
                tips=tips,
                status=status,
                device=ClientEnum.get_msg_by_code(item.get("device")),
                login_host=item["login_host"],
                surplus_time=surplus_time,
                create_time=TimeUtil.timestamp_to_date(item.get("create_time")) if item.get("create_time") else "-",
                expire_time=TimeUtil.timestamp_to_date(item.get("expire_time")) if item.get("expire_time") else "-",
                last_op_time=TimeUtil.timestamp_to_date(item.get("last_op_time")) if item.get("last_op_time") else "-",
                last_ip_address=item.get("last_ip_address", ""),
                last_ua_browser=item.get("last_ua_browser", "")
            ))

        return PagingResult.create(
            data=_data,
            total=len(init_sessions),
            page_no=params.page_no,
            page_size=params.page_size
        )

    @classmethod
    async def lists(cls, params: schema.UserSearchIn) -> PagingResult[schema.UserListVo]:
        """
        用户列表。

        Args:
            params (UserSearchIn): 用户查询参数。

        Returns:
            PagingResult[schema.UserListVo]: 用户分页列表Vo。

        Author:
            zero
        """
        # 查询条件
        where = UserModel.build_search({
            "=": ["is_disable"],
            "%like%": ["keyword@sn|nickname|mobile"],
        }, params.__dict__)

        # 查询数据
        _model = UserModel.filter(is_delete=0).filter(*where).order_by("-id")
        _pager = await UserModel.paginate(
            model=_model,
            page_no=params.page_no,
            page_size=params.page_size,
            fields=UserModel.without_field("salt,password,is_delete,delete_time")
        )

        # 查询分组
        group_ids = [item["group_id"] for item in _pager.lists if item["group_id"]]
        groups = await UserGroupModel.queryGroupDictsById(group_ids)

        # 处理数据
        _lists = []
        for item in _pager.lists:
            quota: Dict[str, Dict[str, int]] = await SubscribeLogic.get_user_quota(item["id"])

            item["group"] = groups.get(item["group_id"], "-")
            item["email"] = item["email"] if item["email"] else "-"
            item["mobile"] = item["mobile"] if item["mobile"] else "-"
            item["avatar"] = await UrlUtil.to_absolute_url(item["avatar"])

            # 套餐包含
            item["pack_know_num"] = quota["know"]["pack_num"]
            item["pack_robot_num"] = quota["robot"]["pack_num"]
            item["pack_train_num"] = quota["train"]["pack_num"]
            item["pack_mcp_num"] = quota["mcp"]["pack_num"]
            # 已经使用
            item["usage_know_num"] = quota["know"]["usage_num"]
            item["usage_robot_num"] = quota["robot"]["usage_num"]
            item["usage_train_num"] = quota["train"]["usage_num"]
            item["usage_mcp_num"] = quota["mcp"]["usage_num"]
            # 当前会员
            item["member_package"] = quota["member"]["name"]
            item["member_end_time"] = quota["member"]["end_time"]

            _lists.append(TypeAdapter(schema.UserListVo).validate_python(item))

        _pager.lists = _lists
        return _pager

    @classmethod
    async def detail(cls, id_: int) -> schema.UserDetailVo:
        """
        用户详情。

        Args:
            id_ (int): 用户ID。

        Returns:
            schema.UserDetailVo: 用户详情Vo。

        Author:
            zero
        """
        user = await UserModel.get(id=id_)

        quota: Dict[str, Dict[str, int]] = await SubscribeLogic.get_user_quota(id_)

        detail = user.__dict__
        detail["gender"] = GenderEnum.get_msg_by_code(user.gender)
        detail["create_time"] = TimeUtil.timestamp_to_date(user.create_time)
        detail["last_login_time"] = TimeUtil.timestamp_to_date(user.last_login_time)
        detail["avatar"] = await UrlUtil.to_absolute_url(user.avatar)
        # 已经使用
        detail["usage_know_num"] = quota["know"]["usage_num"]
        detail["usage_robot_num"] = quota["robot"]["usage_num"]
        detail["usage_train_num"] = quota["train"]["usage_num"]
        detail["usage_mcp_num"] = quota["mcp"]["usage_num"]
        # 套餐包含
        detail["pack_know_num"] = quota["know"]["pack_num"]
        detail["pack_robot_num"] = quota["robot"]["pack_num"]
        detail["pack_train_num"] = quota["train"]["pack_num"]
        detail["pack_mcp_num"] = quota["mcp"]["pack_num"]
        # 当前会员
        detail["member_package"] = quota["member"]["name"]
        detail["member_end_time"] = quota["member"]["end_time"]
        return TypeAdapter(schema.UserDetailVo).validate_python(detail)

    @classmethod
    async def subscribe(cls, user_id: int) -> List[schema.UserSubscribeVo]:
        """
        订阅记录 (生效中/待生效)

        Args:
            user_id (int): 用户ID。

        Returns:
            List[schema.UserSubscribeVo]: 记录列表VO。

        Author:
            zero
        """
        lists = await (UserSubscribeModel
                       .filter(is_delete=0)
                       .filter(user_id=user_id)
                       .filter(expire_time__gt=int(time.time()))
                       .order_by("effect_time")
                       .all())

        package_ids = [item.package_id for item in lists]
        package_arr = await SubscribePackageModel.filter(id__in=package_ids).values("id", "name")
        packages = {item["id"]: item["name"] for item in package_arr}

        _lists = []
        for index, item in enumerate(lists):
            _lists.append(schema.UserSubscribeVo(
                name=packages.get(item.package_id, "-"),
                is_effect=True if index == 0 else False,
                is_perpetual=bool(item.is_perpetual),
                effect_time=TimeUtil.timestamp_to_date(item.effect_time),
                expire_time=TimeUtil.timestamp_to_date(item.expire_time)
            ))

        return _lists

    @classmethod
    async def create(cls, post: schema.UserCreateIn):
        """
        用户创建。

        Args:
            post (schema.UserCreateIn): 用户创建参数。

        Author:
            zero
        """
        # 创建账号
        await UserWidget.create_user({
            "account": post.account,
            "mobile": post.mobile,
            "password": post.password,
            "terminal": ClientEnum.PC
        })

    @classmethod
    async def edit(cls, user_id: int, field: str, value: str):
        """
        用户编辑。

        Args:
            user_id (int): 用户ID。
            field (str): 要更新的字段名。
            value (str): 要更新的字段值。

        Author:
            zero
        """
        await UserModel.filter(id=user_id, is_delete=0).get()
        if field in ["account", "nickname"]:
            await cls._update_user_field(user_id, field, value)
        elif field == "gender":
            if value not in ["0", "1", "2"]:
                raise AppException("请正确选择您的性别")
            await cls._update_user_field(user_id, field, value)
        elif field == "mobile":
            if not ValidUtils.is_mobile(value):
                raise AppException("不是合法的手机号")
            await cls._update_user_field(user_id, field, value)
        elif field == "email":
            if not ValidUtils.is_email(value):
                raise AppException("不是合法的邮箱号")
            await cls._update_user_field(user_id, field, value)
        else:
            raise AppException("字段不支持场景: [account,nickname,gender,mobile,email]")

    @classmethod
    async def blacklist(cls, user_id: int):
        """
        拉黑名单

        Args:
            user_id (int): 用户ID。

        Author:
            zero
        """
        user = await UserModel.filter(id=user_id, is_delete=0).get()
        user.is_disable = 0 if user.is_disable else 1
        user.update_time = int(time.time())
        await user.save()
        await cls.kick_out(user_id, "all")

    @classmethod
    async def change_group(cls, user_id: int, group_id: int):
        """
        分组修改。

        Args:
            user_id (int): 用户ID。
            group_id (int): 分组ID。

        Author:
            zero
        """
        user = await UserModel.filter(id=user_id, is_delete=0).get()
        if group_id:
            group = await UserGroupModel.filter(id=group_id, is_delete=0).first()
            if not group:
                raise AppException("分组不存在")

        user.group_id = group_id
        user.update_time = int(time.time())
        await user.save()

    @classmethod
    async def reset_password(cls, user_id: int, new_password: str):
        """
        重置密码。

        Args:
            user_id (int): 用户ID。
            new_password (str): 新密码。

        Author:
            zero
        """
        await UserModel.filter(id=user_id, is_delete=0).get()

        salt, password = ToolsUtil.make_md5_pwd(new_password)
        await UserModel.filter(id=user_id).update(
            salt=salt,
            password=password,
            update_time=int(time.time())
        )
        await cls.kick_out(user_id, "all")

    @classmethod
    async def adjust_account(cls, user_id: int, scene: str, action: str, amount: int):
        """
        调整账户。

        Args:
            user_id (int): 用户ID。
            scene (str): 变动场景: [points,robot,know,train]。
            action (str): 变动类型: [inc,dec,final]。
            amount (points): 变动数量。

        Author:
            zero
        """
        user = await UserModel.filter(id=user_id, is_delete=0).get()
        async with in_transaction("mysql"):
            if scene == "points":
                t = WalletEnum.POINTS
                INC = WalletEnum.POINTS_INC_ADMIN
                DEC = WalletEnum.POINTS_DEC_ADMIN
                if action == "inc":
                    await UserModel.filter(id=user_id).update(points=(user.points + amount))
                    await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                elif action == "dec":
                    if not (user.points - amount):
                        raise AppException("当前积分数量不足以扣减")
                    await UserModel.filter(id=user_id).update(points=(user.points - amount))
                    await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
                elif action == "final":
                    if user.points == amount:
                        raise AppException("积分数量没有任何变动哦")
                    elif user.points < amount:
                        await UserModel.filter(id=user_id).update(points=(user.points - amount))
                        await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                    elif user.points > amount:
                        await UserModel.filter(id=user_id).update(points=(user.points - amount))
                        await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
            elif scene == "train":
                t = WalletEnum.TRAIN
                INC = WalletEnum.TRAIN_DEC_ADMIN
                DEC = WalletEnum.TRAIN_INC_ADMIN
                if action == "inc":
                    await UserModel.filter(id=user_id).update(train_num=(user.train_num + amount))
                    await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                elif action == "dec":
                    if not (user.train_num - amount):
                        raise AppException("当前训练组数量不足以扣减")
                    await UserModel.filter(id=user_id).update(train_num=(user.train_num - amount))
                    await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
                elif action == "final":
                    if user.train_num == amount:
                        raise AppException("训练组数量没有任何变动哦")
                    elif user.train_num < amount:
                        await UserModel.filter(id=user_id).update(train_num=(user.train_num - amount))
                        await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                    elif user.train_num > amount:
                        await UserModel.filter(id=user_id).update(train_num=(user.train_num - amount))
                        await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
            elif scene == "robot":
                t = WalletEnum.ROBOT
                INC = WalletEnum.ROBOT_DEC_ADMIN
                DEC = WalletEnum.ROBOT_INC_ADMIN
                if action == "inc":
                    await UserModel.filter(id=user_id).update(robot_num=(user.robot_num + amount))
                    await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                elif action == "dec":
                    if not (user.robot_num - amount):
                        raise AppException("当前智能体数量不足以扣减")
                    await UserModel.filter(id=user_id).update(robot_num=(user.robot_num - amount))
                    await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
                elif action == "final":
                    if user.robot_num == amount:
                        raise AppException("智能体数量没有任何变动哦")
                    elif user.robot_num < amount:
                        await UserModel.filter(id=user_id).update(robot_num=(user.robot_num - amount))
                        await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                    elif user.robot_num > amount:
                        await UserModel.filter(id=user_id).update(robot_num=(user.robot_num - amount))
                        await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
            elif scene == "know":
                t = WalletEnum.KNOW
                INC = WalletEnum.KNOW_DEC_ADMIN
                DEC = WalletEnum.KNOW_INC_ADMIN
                if action == "inc":
                    await UserModel.filter(id=user_id).update(know_num=(user.know_num + amount))
                    await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                elif action == "dec":
                    if not (user.know_num - amount):
                        raise AppException("当前知识库数量不足以扣减")
                    await UserModel.filter(id=user_id).update(know_num=(user.know_num - amount))
                    await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
                elif action == "final":
                    if user.know_num == amount:
                        raise AppException("知识库数量没有任何变动哦")
                    elif user.know_num < amount:
                        await UserModel.filter(id=user_id).update(know_num=(user.know_num - amount))
                        await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                    elif user.know_num > amount:
                        await UserModel.filter(id=user_id).update(know_num=(user.know_num - amount))
                        await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
            elif scene == "mcp":
                t = WalletEnum.MCP
                INC = WalletEnum.MCP_DEC_ADMIN
                DEC = WalletEnum.MCP_INC_ADMIN
                if action == "inc":
                    print((user.mcp_num + amount))
                    await UserModel.filter(id=user_id).update(mcp_num=(user.mcp_num + amount))
                    await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                elif action == "dec":
                    if not (user.mcp_num - amount):
                        raise AppException("当前MCP数量不足以扣减")
                    await UserModel.filter(id=user_id).update(mcp_num=(user.mcp_num - amount))
                    await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)
                elif action == "final":
                    if user.mcp_num == amount:
                        raise AppException("MCP数量没有任何变动哦")
                    elif user.mcp_num < amount:
                        await UserModel.filter(id=user_id).update(mcp_num=(user.mcp_num - amount))
                        await UserWalletModel.inc(scene=t, user_id=user_id, change_type=INC, change_amount=amount)
                    elif user.mcp_num > amount:
                        await UserModel.filter(id=user_id).update(mcp_num=(user.mcp_num - amount))
                        await UserWalletModel.dec(scene=t, user_id=user_id, change_type=DEC, change_amount=amount)

    @classmethod
    async def kick_out(cls, user_id: int, uuid_: str):
        """
        强制下线。

        Args:
            user_id (int): 用户ID。
            uuid_ (str): 登录标识。

        Author:
            zero
        """
        if uuid_ == "all":
            await SecurityDriver.module("api").logout(user_id)
        else:
            sessions = await SecurityDriver.module("api").get_token_list(user_id)
            for item in sessions:
                if item["key"] == uuid_:
                    await SecurityDriver.module("api").logout_by_token(item["value"])
                    break

    @classmethod
    async def _update_user_field(cls, user_id: int, field: str, value: str):
        """
        更新用户字段的通用方法。

        Args:
            user_id (int): 用户ID。
            field (str): 要更新的字段名。
            value (str): 要更新的字段值。

        Author:
            zero
        """
        _lang = {"account": "登录账号", "nickname": "用户昵称", "mobile": "手机号", "email": "邮箱号"}
        if field == "account" or field == "nickname":
            if len(value) < 4:
                raise AppException(f"{_lang[field]}长度不能少于4个字符")
            if len(value) > 20:
                raise AppException(f"{_lang[field]}长度不能大于20个字符")

            user = await UserModel.filter(id__not=user_id, **{field: value}, is_delete=0).first()
            if user:
                raise AppException(f"该{_lang[field]}已被占用了")

        await UserModel.filter(id=user_id).update(**{field: value, "update_time": int(time.time())})
